package flex.cc.vehicle_svr.service.impl;

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import flex.cc.PubUtils;
import flex.cc.PublicUtil;
import flex.cc.basicinformation.dao.ChannelDao;
import flex.cc.basicinformation.entity.ChannelDataEntity;
import flex.cc.basicinformation.entity.ChannelEntity;
import flex.cc.basicinformation.service.ChannelService;
import flex.cc.bdmap.dao.MasterpointsDao;
import flex.cc.bdmap.entity.MasterpointsEntity;
import flex.cc.bdmap.vo.MasterpointsVo;
import flex.cc.bdmap.vo.pointsVo;
import flex.cc.common.Constants;
import flex.cc.common.utils.DateUtil;
import flex.cc.common.utils.HttpClientUtil;
import flex.cc.config.Config;
import flex.cc.order.manage.dao.OrderMainDao;
import flex.cc.order.manage.dao.OrderRescueDao;
import flex.cc.order.manage.dao.OrderServiceProviderDao;
import flex.cc.order.manage.dto.order.OrderMainEntity;
import flex.cc.order.manage.dto.order.OrderRescueEntity;
import flex.cc.order.manage.dto.order.OrderServiceProviderEntity;
import flex.cc.vehicle_svr.model.masterpoints.RescueTrackResModel;
import flex.cc.vehicle_svr.model.masterpoints.TokenModel;
import flex.cc.vehicle_svr.model.masterpoints.TokenResModel;
import flex.cc.vehicle_svr.service.MasterpointsToUpService;
import flex.cc.vehicle_svr.util.UpPointsApiAESUtils;
import flex.cc.vehicle_svr.vo.ResponseVo;


@Service("masterpointsToUpService")
public class MasterpointsToUpServiceImpl extends ServiceImpl<MasterpointsDao, MasterpointsEntity> implements MasterpointsToUpService {
	
    private static final Logger logger = LoggerFactory.getLogger(MasterpointsToUpServiceImpl.class);
	
	@Autowired
	private MasterpointsDao masterpointsDao;
	
	@Autowired
	private OrderMainDao orderMainDao;
	
	@Autowired
	private OrderRescueDao orderRescueDao;
	
	@Autowired
	private ChannelDao channelDao;
	
	@Autowired
	private OrderServiceProviderDao orderServiceProviderDao;
	
	@Resource
	private ChannelService channelService;
	/**
     * 回写执行最大次数
     */
    private static final int MAXCOUNT = 3;
    
    /**
     * 两次回写间隔，单位：毫秒
     */
    private static final int SLEEPTIME = 5000;
    
    /**
     * 上游返回的token值，从上游获取，有有效期
     */
    private static String token = "";
    
    /**
     * 上游返回的token值的有效期
     */
    private static long expireTime = 0;
    
	@Override
	@Transactional
	@Async
	public void sendMasterpointsToUp(MasterpointsVo masterpointsVo) {
		logger.info("给上游回写师傅坐标任务-开始执行:orderNo={}", masterpointsVo.getOrderNo());
		try {
			// 查询订单信息
			OrderMainEntity orderMain = null;
			OrderRescueEntity orderRescue = null;
			ChannelEntity orderChannel = null;
			String trackType = "";
			try {
				HashMap<String, Object> queryOrderParam = new HashMap<>();
				queryOrderParam.put("orderNo", masterpointsVo.getOrderNo());
				orderMain = orderMainDao.getOneByParams(queryOrderParam);
				if(orderMain != null && PublicUtil.isNotEmpty(orderMain.getOrderNo())) {
					
					for(pointsVo pointsvo : masterpointsVo.getPoints()) {
						MasterpointsEntity masterpointsEntity=new MasterpointsEntity();
						masterpointsEntity.setOrderNo(masterpointsVo.getOrderNo());
						masterpointsEntity.setLongitude(pointsvo.getLongitude());
						masterpointsEntity.setLatitude(pointsvo.getLatitude());
						masterpointsEntity.setAddress(pointsvo.getAddress()); 	// 坐标对应地址
						masterpointsEntity.setCdate(pointsvo.getCdate());		// 坐标采集时间：yyyy-MM-dd HH:mm:ss
						masterpointsEntity.setEta(pointsvo.getEta()); 			// 救援师傅预计到达客户时间（秒）
						masterpointsEntity.setEda(pointsvo.getEda()); 			// 救援师傅预计到达客户距离（米）
						masterpointsEntity.setVersion(1);
						masterpointsEntity.setCreater("sendMasterPoints接口");
						masterpointsEntity.setCreateTime(new Date());
						masterpointsEntity.setUpdater("sendMasterPoints接口");
						masterpointsEntity.setUpdateTime(new Date());
						masterpointsDao.insert(masterpointsEntity);
					}
					
					//渠道来源  
					String sourceName = orderMain.getSourceName();
					if("02".equals(sourceName)){
						//中华新渠道
						sendMasterpointsToZH(masterpointsVo,orderMain.getOwnerOrderNo(),sourceName);
						return;
					}
					
					// 判断轨迹类型: 救援类型为拖车（FSG005-拖车服务）、涉水拖车（FSG011-城市涉水拖车） 并且 状态为06-到达现场（即：状态回写过06救援师傅到达现场开始作业），则为2-拖车轨迹，否则为1-救援轨迹
					if(("FSG005".equals(orderMain.getServiceItem()) || "FSG011".equals(orderMain.getServiceItem())) && "06".equals(orderMain.getStatus())) {
						trackType = "2";
					} else {
						trackType = "1";
					}
					// 查询救援地址
					HashMap<String, Object> queryRescueParam = new HashMap<>();
					queryRescueParam.put("orderNo", masterpointsVo.getOrderNo());
		            orderRescue = orderRescueDao.getOneByParams(queryRescueParam);
					// 查询渠道商信息
		            orderChannel = new ChannelEntity();
		            orderChannel.setChannelCode(orderMain.getSourceName());
		    		List<ChannelEntity> channelList = channelDao.selectChanneName(orderChannel);
		    		orderChannel = PublicUtil.isNotEmpty(channelList) ? channelList.get(0) : null;
				}
			} catch(Exception e) {
				logger.error("给上游回写师傅坐标任务-查询基础信息异常:orderNo={}", masterpointsVo.getOrderNo(), e);
			}
			if(orderMain != null) {
				// 计算这批坐标的最新时间
				String newTime = "";
				for(pointsVo point : masterpointsVo.getPoints()) {
					if(PublicUtil.isEmpty(newTime) || (PublicUtil.isNotEmpty(point.getCdate()) && point.getCdate().compareTo(newTime) > 0)) {
						newTime = point.getCdate();
					}
				}
				newTime = PublicUtil.isEmpty(newTime) ? DateUtil.getStrDate(new Date(), "yyyy-MM-dd HH:mm:ss") : newTime;
				// 循环回写，成功即返回
				int count = 0; // 已执行次数
				while(count < MAXCOUNT) {
					count++; // 执行次数+1
					logger.info("给上游回写师傅坐标任务-第{}次回写开始:orderNo={}，time={}", count, masterpointsVo.getOrderNo(), newTime);
					try {
						/** 如果token已失效，则调用上游接口获取token */
			    		if(System.currentTimeMillis() > expireTime) {
			    			JSONObject jsonObject = new JSONObject();
							jsonObject.put("passWord", Config.upPointsPassWord);
							jsonObject.put("channel", Config.upPointsChannel);
							String paramStr = UpPointsApiAESUtils.encryptWithHEX(jsonObject.toString(), Config.upPointsUrlKey);
							Map<String, String> tokenReqMap = new HashMap<String, String>();
							tokenReqMap.put("sysCode", Config.upPointsSysCode);
							tokenReqMap.put("paramStr", paramStr);
							String tokenReqJson = JSON.toJSONString(tokenReqMap);
							// 调用token接口
							logger.info("给上游回写师傅坐标任务-第{}次回写获取token:orderNo={}，url={}，tokenReqJson={}", count, masterpointsVo.getOrderNo(), Config.upPointsTokenUrl, tokenReqJson);
							String tokenResJson = HttpClientUtil.sendHttpPost(Config.upPointsTokenUrl, tokenReqJson, null);
							logger.info("给上游回写师傅坐标任务-第{}次回写获取token:orderNo={}，tokenResJson={}", count, masterpointsVo.getOrderNo(), tokenResJson);
							// 解析返回报文
							if(PublicUtil.isNotEmpty(tokenResJson)) {
								// 调用下游失败也会默认返回失败报文，因为结构不同所以通过此属性判断是否调用成功
								if(tokenResJson.indexOf("status") > 0) {
									TokenResModel tokenResModel = JSON.parseObject (tokenResJson, TokenResModel.class);
									if(tokenResModel.isStatus()) {
										String result = tokenResModel.getResult();
										String tokenJson = UpPointsApiAESUtils.decryptWithHEX(result, Config.upPointsUrlKey);
										logger.info("给上游回写师傅坐标任务-第{}次回写获取token:orderNo={}，tokenJson={}", count, masterpointsVo.getOrderNo(), tokenJson);
										TokenModel tokenModel = JSON.parseObject (tokenJson, TokenModel.class);
										token = tokenModel.getToken();
										// 因为自己处理也会耗费一定时间，所以有效时间减去10分钟
										expireTime = System.currentTimeMillis() + (tokenModel.getExpireTime() * 1000) - 600000;
									} else {
										logger.info("给上游回写师傅坐标任务-第{}次回写获取token失败:orderNo={}，获取token信息上游返回失败", count, masterpointsVo.getOrderNo());
										Thread.sleep(SLEEPTIME); // 休眠一段时间后再继续回写
										continue;
									}
								} else {
									logger.info("给上游回写师傅坐标任务-第{}次回写获取token失败:orderNo={}，获取token信息调用上游失败", count, masterpointsVo.getOrderNo());
									Thread.sleep(SLEEPTIME); // 休眠一段时间后再继续回写
									continue;
								}
							} else {
								logger.info("给上游回写师傅坐标任务-第{}次回写获取token失败:orderNo={}，获取token信息返回报文为空", count, masterpointsVo.getOrderNo());
								Thread.sleep(SLEEPTIME); // 休眠一段时间后再继续回写
								continue;
							}
			    		}
						
						// 判断是否有更新的坐标回写
						int newPointCount = masterpointsDao.getNewPointCount(masterpointsVo.getOrderNo(), newTime);
						/** 如果没有最新的坐标回写，则调用上游接口回写此坐标 */
						if(newPointCount == 0) {
							// 调用上游接口回写坐标
							List<Map<String, String>> points = new ArrayList<Map<String, String>>();
							for(pointsVo pointVo : masterpointsVo.getPoints()) {
								Map<String, String> pointMap = new HashMap<String, String>();
								pointMap.put("rescueNo", orderMain.getOwnerOrderNo());
								pointMap.put("longitude", pointVo.getLongitude().toString());
								pointMap.put("latitude", pointVo.getLatitude().toString());
								pointMap.put("trackType", trackType);
								pointMap.put("distance", PublicUtil.isEmpty(pointVo.getEda()) ? "0" : pointVo.getEda().toString());
								pointMap.put("time", PublicUtil.isEmpty(pointVo.getEta()) ? "0" : pointVo.getEta());
								pointMap.put("destination", orderRescue.getDesAddress());
								pointMap.put("source", orderChannel.getChannelName());
								pointMap.put("generationTime", PublicUtil.isEmpty(pointVo.getCdate()) ? newTime : pointVo.getCdate());
								points.add(pointMap);
							}
							Map<String, Object> trackReqMap = new HashMap<String, Object>();
							trackReqMap.put("data", points);
							String trackReqJson = JSON.toJSONString(trackReqMap);
							Map<String, String> headerMap = new HashMap<String, String>(){{
								put("Content-Type", "application/json;charset=utf-8");
								put("cshToken", token);
								put("channel", Config.upPointsChannel);
								put("sysCode", Config.upPointsSysCode);
								put("transTime", DateUtil.getStrDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
								put("requestSeq", PubUtils.uuid());
								put("version", "1.0");
					        }};
					        logger.info("给上游回写师傅坐标任务-第{}次回写:orderNo={}，url={}，header={}，reqJson={}", count, masterpointsVo.getOrderNo(), Config.upPointsTrackUrl, JSON.toJSONString(headerMap), trackReqJson);
					        // 调用回写接口
							String trackResJson = HttpClientUtil.sendHttpPost(Config.upPointsTrackUrl, trackReqJson, headerMap);
							logger.info("给上游回写师傅坐标任务-第{}次回写:orderNo={}，resJson={}", count, masterpointsVo.getOrderNo(), trackResJson);
							// 解析返回报文
							if(PublicUtil.isNotEmpty(trackResJson)) {
								// 调用下游失败也会默认返回失败报文，因为结构不同所以通过此属性判断是否调用成功
								if(trackResJson.indexOf("status") > 0) {
									RescueTrackResModel rescueTrackResModel = JSON.parseObject (trackResJson, RescueTrackResModel.class);
									if(rescueTrackResModel.isStatus()) {
										logger.info("给上游回写师傅坐标任务-第{}次回写成功:orderNo={}", count, masterpointsVo.getOrderNo());
										break;
									}else {
										// 返回307（token无效，会话超时，请重新登录！）重置有效时间
										if("307".equals(rescueTrackResModel.getErrcode())) {
											expireTime = 0;
										}
										logger.info("给上游回写师傅坐标任务-第{}次回写失败:orderNo={}，上游返回失败", count, masterpointsVo.getOrderNo());
										Thread.sleep(SLEEPTIME); // 休眠一段时间后再继续回写
										continue;
									}
								} else {
									logger.info("给上游回写师傅坐标任务-第{}次回写失败:orderNo={}，调用上游失败", count, masterpointsVo.getOrderNo());
									Thread.sleep(SLEEPTIME); // 休眠一段时间后再继续回写
									continue;
								}
							} else {
								logger.info("给上游回写师傅坐标任务-第{}次回写失败:orderNo={}，返回报文为空", count, masterpointsVo.getOrderNo());
								Thread.sleep(SLEEPTIME); // 休眠一段时间后再继续回写
								continue;
							}
						} else {
							logger.info("给上游回写师傅坐标任务-第{}次回写失败:orderNo={}，有新的坐标回写，本次放弃", count, masterpointsVo.getOrderNo());
							break;
						}
					} catch(Exception e) {
						logger.error("给上游回写师傅坐标任务-第{}次回写异常:orderNo={}", count, masterpointsVo.getOrderNo(), e);
						Thread.sleep(SLEEPTIME); // 休眠一段时间后再继续回写
						continue;
					}
				}
			} else {
				logger.info("给上游回写师傅坐标任务执行失败:orderNo={}，查询orderMain为null" + masterpointsVo.getOrderNo());
			}
		} catch (Exception e) {
			logger.error("给上游回写师傅坐标任务-执行异常:orderNo={}", masterpointsVo.getOrderNo(), e);
		}
	}
	
	/**
	 * 中华新渠道回写师傅坐标
	 * @param masterpointsVo 下游回传坐标实体类
	 * @param ownerOrderNo 增值服务系统生成工单
	 * @param sourceName  渠道来源 02-中华
	 */
	public void sendMasterpointsToZH(MasterpointsVo masterpointsVo,String ownerOrderNo,String sourceName) {
		try {
			//救援单号
			String orderNo = masterpointsVo.getOrderNo();
			// 计算这批坐标的最新时间
			String newTime = "";
			// 经度GCJ02坐标系
			Double longitude = null;
			// 纬度GCJ02坐标系
			Double latitude = null;
			for(pointsVo point : masterpointsVo.getPoints()) {
				if(PublicUtil.isEmpty(newTime) || (PublicUtil.isNotEmpty(point.getCdate()) && point.getCdate().compareTo(newTime) > 0)) {
					newTime = point.getCdate();
					longitude = point.getLongitude();
					latitude = point.getLatitude();
				}
			}
			NumberFormat formatter = NumberFormat.getNumberInstance();
			//最多保留8位小数
			formatter.setMaximumFractionDigits(8);
			//小于8位保持不变
			formatter.setMinimumFractionDigits(0);
			String longitudeStr = formatter.format(longitude);
			String latitudeStr = formatter.format(latitude);
			
			//获取当前救援单的救援人员以及电话
			Map<String, Object> params = new HashMap<String, Object>();
			params.put("orderNo", orderNo);
			OrderServiceProviderEntity orderServiceProviderEntity = orderServiceProviderDao.getOneByParams(params);
			//救援师傅名称
			String rescueName = orderServiceProviderEntity.getRescueName();
			//救援师傅电话
			String rescuePhone = orderServiceProviderEntity.getRescuePhone();
			
			
			//根据clientId去查渠道商信息
			Map<String, String> channelMap = new HashMap<>();
			channelMap.put("clientId", sourceName);
			List<ChannelEntity> channelList = channelService.findChannelByParam(channelMap);
			if(channelList.size()==0 || channelList==null){
				logger.info("给上游回写师傅坐标任务-回写失败:orderNo={}，未找到渠道商，本次放弃", masterpointsVo.getOrderNo());
				return;
			}
			String channelId = channelList.get(0).getId().toString();
			List<ChannelDataEntity> channelDataList = channelService.queryListByYwId(channelId);
			String synCoordinate_url = "";
			for(int i=0; i<channelDataList.size(); i++){
	    		if(Constants.synCoordinate.equals(channelDataList.get(i).getUrlType())){
	    			synCoordinate_url = channelDataList.get(i).getUrl();
	    			break;
	    		}
	    	}
			
			newTime = PublicUtil.isEmpty(newTime) ? DateUtil.getStrDate(new Date(), "yyyy-MM-dd HH:mm:ss") : newTime;
			// 循环回写，成功即返回
			int count = 0; // 已执行次数
			while(count < MAXCOUNT) {
				count++; // 执行次数+1
				logger.info("给上游回写师傅坐标任务-第{}次回写开始:orderNo={}，time={}", count, masterpointsVo.getOrderNo(), newTime);
				try {
					// 判断是否有更新的坐标回写
					int newPointCount = masterpointsDao.getNewPointCount(masterpointsVo.getOrderNo(), newTime);
					/** 如果没有最新的坐标回写，则调用上游接口回写此坐标 */
					if(newPointCount == 0) {
						// 调用上游接口回写坐标
						Map<String, Object> masterpointMap = new HashMap<String, Object>();
						masterpointMap.put("workId", ownerOrderNo);
						masterpointMap.put("orderId", orderNo);
						masterpointMap.put("longitude", longitudeStr);
						masterpointMap.put("latitude", latitudeStr);
						masterpointMap.put("sendTime", DateUtil.getCurrentDate("yyyy-MM-dd HH:mm:ss"));
						masterpointMap.put("servicePerName", rescueName);
						masterpointMap.put("servicePerPhone", rescuePhone);
						String requestJson = JSON.toJSONString(masterpointMap);
						
				        logger.info("给上游回写师傅坐标任务-第{}次回写:orderNo={}，url={}，reqJson={}", count, masterpointsVo.getOrderNo(), synCoordinate_url, requestJson);
						String trackResJson = HttpClientUtil.getCodeResult(synCoordinate_url, requestJson); 
						logger.info("给上游回写师傅坐标任务-第{}次回写:orderNo={}，resJson={}", count, masterpointsVo.getOrderNo(), trackResJson);
						// 解析返回报文
						if(PublicUtil.isNotEmpty(trackResJson)) {
							// 调用下游失败也会默认返回失败报文，因为结构不同所以通过此属性判断是否调用成功
							if(trackResJson.indexOf("请求成功") > 0) {
								logger.info("给上游回写师傅坐标任务-第{}次回写成功:orderNo={}", count, masterpointsVo.getOrderNo());
								break;
							} else {
								logger.info("给上游回写师傅坐标任务-第{}次回写失败:orderNo={}，调用上游失败", count, masterpointsVo.getOrderNo());
								Thread.sleep(SLEEPTIME); // 休眠一段时间后再继续回写
								continue;
							}
						} else {
							logger.info("给上游回写师傅坐标任务-第{}次回写失败:orderNo={}，返回报文为空", count, masterpointsVo.getOrderNo());
							Thread.sleep(SLEEPTIME); // 休眠一段时间后再继续回写
							continue;
						}
					} else {
						logger.info("给上游回写师傅坐标任务-第{}次回写失败:orderNo={}，有新的坐标回写，本次放弃", count, masterpointsVo.getOrderNo());
						break;
					}
				} catch(Exception e) {
					logger.error("给上游回写师傅坐标任务-第{}次回写异常:orderNo={}", count, masterpointsVo.getOrderNo(), e);
					Thread.sleep(SLEEPTIME); // 休眠一段时间后再继续回写
					continue;
				}
			}
		}catch (Exception e) {
			logger.error("给上游回写师傅坐标任务-执行异常:orderNo={}", masterpointsVo.getOrderNo(), e);
		}
	}
}